//
//  JFDBSqlite.m
//  JFore
//
//  Created by Synergy_iMac1 on 12-12-12.
//  Copyright (c) 2012年 Synergyinfo. All rights reserved.
//

#import "JFDBSqlite.h"
#import "JFDBStatment.h"
#import <objc/runtime.h>
#import "JFDBUtil.h"
#import "JFDBDef.h"

#define JF_DB_ERROR_DOMAIN @"JfDbErrorDomain"

@implementation JFDBSqlite
{
    sqlite3* m_Db;
    NSString* m_Path;
}

+ (NSArray *)createTableSqls:(NSArray *)entities
{
    if (entities == nil)
    {
        return nil;
    }
    
    NSMutableArray* createTableSqls = [[NSMutableArray alloc]initWithCapacity:entities.count];
    
    for (Class entityClass in entities)
    {
        unsigned int propertyCount;
        objc_property_t* propertyList = class_copyPropertyList(entityClass,&propertyCount);
        if (propertyCount > 0)
        {
            NSMutableString* createTableSql = [[NSMutableString alloc]initWithFormat:@"create table %s(",class_getName(entityClass)];
            BOOL isFirstProperty = YES;
            for (int i = 0;i < propertyCount;i++)
            {
                NSString* propertyType = [JFDBUtil getProperyTypeString:propertyList[i]];
                if (propertyType != nil)
                {
                    const char* propertyName = property_getName(propertyList[i]);
                    if (!isFirstProperty)
                    {
                        [createTableSql appendString:@","];
                    }
                    else
                    {
                        isFirstProperty = NO;
                    }
                    if (strcmp(propertyName,"_id") == 0)
                    {
                        [createTableSql appendFormat:@"%s %@ primary key",propertyName,propertyType];
                    }
                    else
                    {
                        [createTableSql appendFormat:@"%s %@",propertyName,propertyType];
                    }
                }
            }
            [createTableSql appendString:@")"];
            [createTableSqls addObject:createTableSql];
        }
    }
    return createTableSqls;
}

- (id)initWithPath:(NSString *)path
{
    self = [super init];
    if (self)
    {
        m_Db = NULL;
        m_Path = path;
    }
    return self;
}

- (sqlite3 *)db
{
    return m_Db;
}
- (NSString *)path;
{
    return m_Path;
}

- (BOOL)openWithConcurrentType:(JFDBConcurentType)type error:(NSError **)error
{
    int mutexFlag = SQLITE_OPEN_FULLMUTEX;
    if (type == kJFDBConcurentTypeMutiThread)
    {
        mutexFlag = SQLITE_OPEN_NOMUTEX;
    }
    if (SQLITE_OK != sqlite3_open_v2([m_Path UTF8String], &m_Db, mutexFlag | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL))
    {
        int errorCode = sqlite3_errcode(m_Db);
        NSString* errorMessage = [[NSString alloc]initWithFormat:@"%s",sqlite3_errmsg(m_Db)];
        if (error != nil)
        {
            *error = [NSError errorWithDomain:JF_DB_ERROR_DOMAIN code:errorCode userInfo:@{NSLocalizedDescriptionKey : errorMessage}];
        }
        sqlite3_close(m_Db);
        return NO;
    }
    return YES;
}

- (BOOL)open:(NSError**)error
{
    return [self openWithConcurrentType:kJFDBConcurentTypeMutiThread error:error];
}

- (BOOL)close
{
    if (SQLITE_OK != sqlite3_close(m_Db))
    {
        return NO;
    }
    return YES;
}

- (BOOL)executeSqls:(NSArray*)sqls
{
    [self beginTransaction];
    for (NSString* sql in sqls)
    {
        if (![self execute:sql error:nil])
        {
            [self rollback];
            return NO;
        }
    }
    [self commit];
    return YES;
}

- (BOOL)execute:(NSString*)sql error:(NSError**)error
{
    if (!sql || !m_Db)
    {
        return NO;
    }
    
    JFLog(@"%@",sql);
    
    char* errorMsg = nil;
    BOOL ret = (SQLITE_OK == sqlite3_exec(m_Db, [sql UTF8String], nil, nil, &errorMsg));
    
    if (!ret)
    {
        int errorCode = sqlite3_errcode(m_Db);
        NSString* errorMessage = [[NSString alloc]initWithFormat:@"%s",errorMsg];
        if (error != nil)
        {
            *error = [NSError errorWithDomain:JF_DB_ERROR_DOMAIN code:errorCode userInfo:@{NSLocalizedDescriptionKey : errorMessage}];
        }
    }
    sqlite3_free(errorMsg);
    
    return ret;
}

- (JFDBStatment*)prepare:(NSString*)sql error:(NSError**)error
{
    if (!sql || !m_Db)
    {
        return nil;
    }
    
    JFLog(@"%@",sql);
    
    sqlite3_stmt* statment = NULL;
    if (SQLITE_OK != sqlite3_prepare_v2(m_Db, [sql UTF8String], -1, &statment, nil))
    {
        int errorCode = sqlite3_errcode(m_Db);
        NSString* errorMessage = [[NSString alloc]initWithFormat:@"%s",sqlite3_errmsg(m_Db)];
        if (error != nil)
        {
            *error = [NSError errorWithDomain:JF_DB_ERROR_DOMAIN code:errorCode userInfo:@{NSLocalizedDescriptionKey : errorMessage}];
        }
        return nil;
    }
    
    return [[JFDBStatment alloc]initWithStatment:statment];
}

- (BOOL)beginTransaction
{
    return [self execute:@"begin transaction" error:nil];
}

- (BOOL)commit
{
    return [self execute:@"commit transaction" error:nil];
}

- (BOOL)rollback
{
    return [self execute:@"rollback transaction" error:nil];
}

- (NSArray*)findAll:(Class)entityClass byConditionSql:(NSString*)conditionSql
{
    return [self findAll:entityClass byConditionSql:conditionSql params:nil];
}

- (NSArray*)findAll:(Class)entityClass byConditionSql:(NSString*)conditionSql params:(NSArray *)params
{
    if (entityClass == nil || m_Db == nil)
    {
        return nil;
    }
    
    if (conditionSql == nil)
    {
        conditionSql = @"";
    }
    
    NSString* sql = [NSString stringWithFormat:@"select * from %s %@",class_getName(entityClass),conditionSql];
    JFDBStatment* statment = [self prepare:sql error:nil];
    
    if (!statment)
    {
        return nil;
    }
    
    if (params)
    {
        NSUInteger paramCount = params.count;
        for (int i = 0;i < paramCount;i++)
        {
            id param = params[i];
            if ([param isKindOfClass:[NSString class]])
            {
                [statment bindStringParam:param index:i + 1];
            }
            else if ([param isKindOfClass:[NSNumber class]])
            {
                NSNumber *number = param;
                if (strcmp([number objCType], @encode(int)) == 0)
                {
                    [statment bindIntParam:number.intValue index:i + 1];
                }
                else
                {
                    [statment bindDoubleParam:number.doubleValue index:i + 1];
                }
            }
        }
    }
    
    int columnCount = [statment columnCount];
    NSMutableArray* columnNames = [[NSMutableArray alloc]initWithCapacity:columnCount];
    for (int i = 0;i < columnCount;i++)
    {
        columnNames[i] = [statment columnName:i];
    }
    
    NSMutableArray* result = [[NSMutableArray alloc]init];
    
    while ([statment hasMoreResults])
    {
        id obj = [[entityClass alloc] init];
        for (int i = 0;i < columnCount;i++)
        {
            id value = nil;
            switch ([statment columnType:i])
            {
                case kJFDBColumnTypeInt:
                    value = [NSNumber numberWithInt:[statment doubleValueOfColumn:i]];
                    break;
                case kJFDBColumnTypeFloat:
                    value = [NSNumber numberWithDouble:[statment doubleValueOfColumn:i]];
                    break;
                case kJFDBColumnTypeString:
                    value = [statment stringValueOfColumn:i];
                    break;
                default:
                    break;
            }
            if (value != nil)
            {
                [obj setValue:value forKey:columnNames[i]];
            }
        }
        [result addObject:obj];
    }
    [statment finialize];
    return result;
}

- (NSArray*)findAll:(Class)entityClass
{
    return [self findAll:entityClass byConditionSql:nil params:nil];
}

- (NSArray*)findAll:(Class)entityClass byDict:(NSDictionary*)condition orderBy:(NSString *)orderBy
{
    NSMutableString* whereSql = [[NSMutableString alloc]initWithString:@""];
    NSMutableArray* params = [[NSMutableArray alloc]init];
    if (condition)
    {
        NSArray* keys = [condition allKeys];
        NSInteger keyCount = keys.count;
        for (int i = 0;i < keyCount;i++)
        {
            if (i == 0)
            {
                [whereSql appendString:@"where"];
            }
            NSString* key = keys[i];
            id value = condition[key];
            [params addObject:value];
            [whereSql appendFormat:@" %@=?",key];
            if (i != keyCount - 1)
            {
                [whereSql appendString:@" and"];
            }
        }
    }
    
    if (orderBy)
    {
        [whereSql appendFormat:@" order by %@",orderBy];
    }
    
    return [self findAll:entityClass byConditionSql:whereSql params:params];
}

- (NSArray*)findAll:(Class)entityClass orderBy:(NSString *)orderBy
{
    return [self findAll:entityClass byDict:nil orderBy:orderBy];
}

- (NSArray*)findAll:(Class)entityClass byDict:(NSDictionary*)condition
{
    return [self findAll:entityClass byDict:condition orderBy:nil];
}

- (id)find:(Class)entityClass byConditionSql:(NSString*)conditionSql
{
    NSArray* array = [self findAll:entityClass byConditionSql:conditionSql];
    return [array firstObject];
}

- (id)find:(Class)entityClass byConditionSql:(NSString*)conditionSql params:(NSArray *)params
{
    NSArray* array = [self findAll:entityClass byConditionSql:conditionSql params:params];
    return [array firstObject];
}

- (id)find:(Class)entityClass byDict:(NSDictionary*)condition
{
    NSArray* array = [self findAll:entityClass byDict:condition];
    return [array firstObject];
}

- (id)find:(Class)entityClass orderBy:(NSString *)orderBy
{
    NSArray* array = [self findAll:entityClass orderBy:orderBy];
    return [array firstObject];
}

- (id)find:(Class)entityClass byDict:(NSDictionary*)condition orderBy:(NSString *)orderBy
{
    NSArray* array = [self findAll:entityClass byDict:condition orderBy:orderBy];
    return [array firstObject];
}

- (id)find:(Class)entityClass byId:(NSString*)id
{
    NSArray* array = [self findAll:entityClass byDict:@{@"_id" : id}];
    return [array firstObject];
}

- (BOOL)save:(id)object
{
    if (!object || !m_Db)
    {
        return NO;
    }
    
    Class entityClass = [object class];
    
    NSMutableString* sql = [[NSMutableString alloc]initWithFormat:@"insert into %s values(",class_getName(entityClass)];
    
    unsigned int propertyCount;
    objc_property_t* propertyList = class_copyPropertyList(entityClass,&propertyCount);
    BOOL isFirstProperty = YES;
    for (int i = 0;i < propertyCount;i++)
    {
        JFDBColumnType propertyType = [JFDBUtil getProperyType:propertyList[i]];
        if (propertyType == kJFDBColumnTypeInvalid)
        {
            continue;
        }
        if (!isFirstProperty)
        {
            [sql appendString:@","];
        }
        else
        {
            isFirstProperty = NO;
        }
        NSString* key = [NSString stringWithFormat:@"%s",property_getName(propertyList[i])];
        id value = [object valueForKey:key];
        switch (propertyType)
        {
            case kJFDBColumnTypeString:
                [sql appendFormat:@"'%@'",value];
                break;
            case kJFDBColumnTypeFloat:
            case kJFDBColumnTypeInt:
                [sql appendFormat:@"%@",value];
                break;
            default:
                break;
        }
    }
    [sql appendString:@")"];
    
    return [self execute:sql error:nil];
}
- (BOOL)update:(id)object
{
    if (!object || !m_Db)
    {
        return NO;
    }
    
    Class entityClass = [object class];
    
    NSMutableString* sql = [[NSMutableString alloc] initWithFormat:@"update %s set ",class_getName(entityClass)];

    unsigned int propertyCount;
    objc_property_t* propertyList = class_copyPropertyList(entityClass,&propertyCount);
    BOOL isFirstProperty = YES;
    for (int i = 0;i < propertyCount;i++)
    {
        JFDBColumnType propertyType = [JFDBUtil getProperyType:propertyList[i]];
        NSString* key = [NSString stringWithFormat:@"%s",property_getName(propertyList[i])];
        if ([key isEqualToString:@"_id"] || propertyType == kJFDBColumnTypeInvalid)
        {
            continue;
        }
        if (!isFirstProperty)
        {
            [sql appendString:@","];
        }
        else
        {
            isFirstProperty = NO;
        }
        id value = [object valueForKey:key];
        switch (propertyType)
        {
            case kJFDBColumnTypeString:
                [sql appendFormat:@"%@='%@'",key,value];
                break;
            case kJFDBColumnTypeFloat:
            case kJFDBColumnTypeInt:
                [sql appendFormat:@"%@=%@",key,value];
                break;
            default:
                break;
        }
    }
    [sql appendFormat:@" where _id='%@'",[object valueForKey:@"_id"]];
    
    return [self execute:sql error:nil];
}
- (BOOL)saveOrUpdate:(id)object
{
    if (![self save:object])
    {
        return [self update:object];
    }
    return YES;
}
- (BOOL)delete:(id)object
{
    if (!object || !m_Db)
    {
        return NO;
    }
    Class entityClass = [object class];
    
    NSString* sql = [NSString stringWithFormat:@"delete from %s where _id='%@'",class_getName(entityClass),[object valueForKey:@"_id"]];
    
    return [self execute:sql error:nil];
}

- (BOOL)delete:(Class)entityClass byId:(NSString*)id
{
    if (!id || !m_Db)
    {
        return NO;
    }
    
    NSString* sql = [NSString stringWithFormat:@"delete from %s where _id='%@'",class_getName(entityClass),id];
    
    return [self execute:sql error:nil];
}

@end
